/*
 * mounttable.cpp
 *
 *  Created on: Dec 29, 2008
 *      Author: yospaly
 */

/*
 * Copyright (c) 2000, Red Hat, Inc.
 *
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 *
 *     A copy of the GNU General Public License can be found at
 *     http://www.gnu.org/
 *
 * Written by DJ Delorie <dj@cygnus.com>
 *
 */

/* The purpose of this file is to hide all the details about accessing
 Cygwin's mount table.  If the format or location of the mount table
 changes, this is the file to change to match it. */

#include <wx/wx.h>
#include <windows.h>
#include "mounttable.h"
#include "common.h"
#include <stdio.h>
#include <stdlib.h>
#include <wx/wfstream.h>
#include <wx/txtstrm.h>
#include <wx/filename.h>

MountTable::MountTable() {
    _cygwin_installed = false;
    set_root_dir();
}

MountTable::~MountTable() {
	// TODO Auto-generated destructor stub
}

/* Used when treating / and \ as equivalent. */
inline bool SLASH_P(wxChar ch) {
	return ch == _T('/') || ch == _T('\\');
}

static void remove1(bool issystem, const wxString& posix) {
	wxString reg = wxString::Format(_T("%s\\Software\\%s\\%s\\%s\\%s"),
	issystem ? _T("HKLM") : _T("HKCU"), CYGWIN_INFO_CYGNUS_REGISTRY_NAME,
			CYGWIN_INFO_CYGWIN_REGISTRY_NAME,
			CYGWIN_INFO_CYGWIN_MOUNT_REGISTRY_NAME, posix.c_str());

	wxRegKey(reg).DeleteSelf();
}

void MountTable::remove(const wxString& posix) {
	remove1(true, posix);
	remove1(false, posix);
	
	mount_hash.erase(posix);
}

static long default_cygdrive(wxRegKey& key) {
	long cygdrive_flags = MountTable::MOUNT_AUTO; // set to default flag
	key.SetValue(CYGWIN_INFO_CYGDRIVE_PREFIX, CYGWIN_INFO_CYGDRIVE_DEFAULT_PREFIX);
	key.SetValue(CYGWIN_INFO_CYGDRIVE_FLAGS, cygdrive_flags);
	
	return cygdrive_flags;
}

/*
 * is_admin () determines whether or not the current user is a member of the
 * Administrators group.  On Windows 9X, the current user is considered an
 * Administrator by definition.
 */
static int is_admin() {
	// Windows 9X users are considered Administrators by definition
	// if (!IsWindowsNT())
	//	return 1;

	// Get the process token for the current process
	HANDLE token;
	BOOL status = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token);
	if (!status)
		return 0;

	// Get the group token information
	UCHAR token_info[1024];
	PTOKEN_GROUPS groups = (PTOKEN_GROUPS) token_info;
	DWORD token_info_len = sizeof(token_info);
	status = GetTokenInformation(token, TokenGroups, token_info,
			token_info_len, &token_info_len);
	CloseHandle(token);
	if (!status)
		return 0;

	// Create the Administrators group SID
	PSID admin_sid;
	SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY};
	status = AllocateAndInitializeSid(&authority, 2,
			SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0,
			0, 0, &admin_sid);
	if (!status)
		return 0;

	// Check to see if the user is a member of the Administrators group
	status = 0;
	for (UINT i = 0; i < groups->GroupCount; i++) {
		if (EqualSid(groups->Groups[i].Sid, admin_sid)) {
			status = 1;
			break;
		}
	}

	// Destroy the Administrators group SID
	FreeSid(admin_sid);

	// Return whether or not the user is a member of the Administrators group
	return status;
}

void MountTable::create_install_root() {
    wxString reg = wxString::Format(_T("%s\\Software\\%s\\%s"),
            _T("HKLM"),
            CYGWIN_INFO_CYGWIN_REGISTRY_NAME,
            CYGWIN_INFO_CYGWIN_SETUP_REGISTRY_NAME
        );

    wxRegKey key(reg);

    if (false != key.Create(true)) {
        // TODO: fatal("mount", rv);
    }

    bool ret = key.SetValue(_T("rootdir"), get_root_dir());

    if (false != ret) {
        // TODO: fatal("mount", rv);
    }

    key.Close();
    // The mount table is already in the right shape at this point.
    // Reading it again is not necessary.
    //read_mounts (std::string ());
}

inline wxChar * find_ws (wxChar* in) {
    while (*in && *in != ' ' && *in != _T('\t'))
        ++in;
    return in;
}

bool MountTable::_from_fstab_line(const wxString& str) {
    wxString line = str;
    line.Trim(false);

    if ( line.StartsWith(_T("#")) || line.IsEmpty() ) {
        return false;
    }

    wxArrayString list = split_string_ws(line);
    size_t list_count = list.GetCount();

    if ( list.GetCount() < 3 ) {
        return false;
    }

    /* First field: Native path. */
    wxString native_path = list[0];
    native_path.Replace(_T("\\040"), _T(" "));

    /* Second field: POSIX path. */
    wxString posix_path = list[1];
    posix_path.Replace(_T("\\040"), _T(" "));

    /* Third field: FS type. */
    wxString fs_type = list[2];
    //wxLogMessage(_T("Split string: \"%s\""), fs_type.c_str());

    if (fs_type.Cmp(_T("cygdrive"))==0) {
        //wxLogMessage(_T("Split string: \"%s\""), native_path.c_str());
        //wxLogMessage(_T("Split string: \"%s\""), posix_path.c_str());

        native_path.Replace(_T("/"), _T("\\"));

        MountPoint mnt;
        mnt.posix = posix_path;
        mnt.native = native_path;
        mount_hash[mnt.posix] = mnt;

        if (mount_hash.find(posix_path) != mount_hash.end()) {
            return false;
        }

        if ( posix_path.Cmp(_T("/usr/bin"))==0 ) {
            _got_usr_bin = true;
        } else if (posix_path.Cmp(_T("/usr/lib")==0) ) {
            _got_usr_lib = true;
        }
    }

    return true;
}

bool MountTable::_from_fstab(const wxString& in_path) {
    wxString path = in_path;
    path.Append(_T("\\etc\\fstab"));

    if (!wxFile::Exists(path)) {
        wxLogMessage(_T("\"%s\" not exists"), path.c_str());
        return false;
    }

    wxLogMessage(_T("Start parsing \"%s\""), path.c_str());

    wxFileInputStream input(path);
    wxTextInputStream text(input);

    while (input.CanRead()) {
        const wxString& line = text.ReadLine();
        _from_fstab_line(line);
    }

    return true;
}

void MountTable::_add_usr_mnts() {
    /* Set default /usr/bin and /usr/lib */

    if (!_got_usr_bin) {
        MountPoint mnt;
        mnt.posix = _T("/usr/bin");
        mnt.native = get_root_dir() + _T("\\bin");
        mount_hash[mnt.posix] = mnt;
    }

    if (!_got_usr_lib) {
        MountPoint mnt;
        mnt.posix = _T("/usr/lib");
        mnt.native = get_root_dir() + _T("\\lib");
        mount_hash[mnt.posix] = mnt;
     }
}

void MountTable::set_root_dir(const wxString& dir) {
	// clear mount point table
    mount_hash.clear();
    _got_usr_lib = _got_usr_bin = false;

    wxString rootdir = dir;

    if (rootdir.IsEmpty()) {
        wxString reg = wxString::Format(_T("%s\\Software\\%s\\%s"),
                _T("HKLM"),
                CYGWIN_INFO_CYGWIN_REGISTRY_NAME,
                CYGWIN_INFO_CYGWIN_SETUP_REGISTRY_NAME
            );

        wxRegKey key(reg);
        // key.Create(true);

        key.QueryValue(_T("rootdir"), rootdir);
        key.Close();
    }

    if (rootdir.IsEmpty()) {
		wxChar windir[MAX_PATH];
		::GetWindowsDirectory(windir, sizeof(windir));
        windir[2] = _T('\0');

        rootdir = wxString(windir);
        rootdir.Append(_T("\\cygwin"));
    }

    MountPoint mnt;
    mnt.posix = _T("/");
    mnt.native = rootdir;
    mount_hash[mnt.posix] = mnt;

    _from_fstab(rootdir);
    _add_usr_mnts();
    _check_cygwin_installed();
}

void MountTable::_check_cygwin_installed() {
    _cygwin_installed = false;

    const wxString& installdb = cygpath(CYGWIN_INSTALLED_DB_PATH);

    if (wxFileName::FileExists(installdb)) {
        _cygwin_installed = true;
    }
}

/* Return non-zero if PATH1 is a prefix of PATH2.
 Both are assumed to be of the same path style and / vs \ usage.
 Neither may be "".

 Examples:
 /foo/ is a prefix of /foo  <-- may seem odd, but desired
 /foo is a prefix of /foo/
 / is a prefix of /foo/bar
 / is not a prefix of foo/bar
 foo/ is a prefix foo/bar
 /foo is not a prefix of /foobar
 */
static bool path_prefix_p(const wxString path1, const wxString path2) {
	size_t len1 = path1.Len();
	/* Handle case where PATH1 has trailing '/' and when it doesn't.  */
	if (len1 > 0 && SLASH_P(path1[len1 - 1]))
		--len1;

	if (len1 == 0)
		return SLASH_P(path2[0]) && !SLASH_P(path2[1]);

	if (path2.Find(path1) != 0) {
		return false;
	}

	return SLASH_P(path2[len1]) || path2.Len() == len1 || path1[len1 - 1]
			== ':';
}

wxString MountTable::cygpath(const wxString& path) {
	MountPointHash::iterator it;
	wxString match;
	size_t max_len = 0;

	for (it = mount_hash.begin(); it != mount_hash.end(); ++it) {
		MountPoint& mnt = it->second;

		size_t n = mnt.posix.Len();
		if (n <= max_len || !path_prefix_p(mnt.posix, path)) {
			continue;
		}

		max_len = n;
		match = it->first;
	}

	if (match.IsEmpty()) {
		return wxString();
	}

	wxString native = mount_hash[match].native;

	if (max_len != path.size()) {
		native.Append(_T("\\"));
		native.Append(path.Mid(max_len));
	}
	
	native.Replace(_("/"), _("\\"));
	native.Replace(_("\\\\"), _("\\"));
	return native;
}

MountTable MountTable::self;

